From 019d5157c1b1fd781773ea204c36a348c679594a Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 22 Oct 2014 23:32:57 +0200 Subject: [PATCH] Custom build commands are now being run --- src/cargo/ops/cargo_rustc/mod.rs | 139 ++++++++++++++++++----- src/cargo/util/toml.rs | 6 +- tests/test_cargo_compile_custom_build.rs | 99 +++++++++++++++- 3 files changed, 215 insertions(+), 29 deletions(-) diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 51d0644cf..774675405 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -115,10 +115,10 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, } let compiled = compiled.contains(dep.get_package_id()); - try!(compile(targets.as_slice(), dep, compiled, &mut cx, &mut queue)); + try!(compile(targets.as_slice(), dep, pkg, compiled, &mut cx, &mut queue)); } - try!(compile(targets, pkg, true, &mut cx, &mut queue)); + try!(compile(targets, pkg, pkg, true, &mut cx, &mut queue)); // Now that we've figured out everything that we're going to do, do it! try!(queue.execute(cx.config)); @@ -127,7 +127,7 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, } fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package, - compiled: bool, + root_pkg: &'a Package, compiled: bool, cx: &mut Context<'a, 'b>, jobs: &mut JobQueue<'a, 'b>) -> CargoResult<()> { debug!("compile_pkg; pkg={}; targets={}", pkg, targets); @@ -153,27 +153,6 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package, } jobs.enqueue(pkg, jq::StageStart, init); - // Old custom build system - // TODO: deprecated, remove - let mut build_cmds = Vec::new(); - for (i, build_cmd) in pkg.get_manifest().get_build().iter().enumerate() { - let work = try!(compile_custom_old(pkg, build_cmd.as_slice(), cx, i == 0)); - build_cmds.push(work); - } - let (freshness, dirty, fresh) = - try!(fingerprint::prepare_build_cmd(cx, pkg)); - let desc = match build_cmds.len() { - 0 => String::new(), - 1 => pkg.get_manifest().get_build()[0].to_string(), - _ => format!("custom build commands"), - }; - let dirty = proc() { - for cmd in build_cmds.into_iter() { try!(cmd()) } - dirty() - }; - jobs.enqueue(pkg, jq::StageCustomBuild, vec![(job(dirty, fresh, desc), - freshness)]); - // After the custom command has run, execute rustc for all targets of our // package. // @@ -187,7 +166,20 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package, vec![(rustdoc, KindTarget, desc)] } else { let req = cx.get_requirement(pkg, target); - try!(rustc(pkg, target, cx, req)) + let mut rustc = try!(rustc(pkg, target, cx, req)); + + if target.get_profile().is_custom_build() { + for &(ref mut work, _, _) in rustc.iter_mut() { + use std::mem; + let execute_cmd = try!(prepare_execute_custom_build(pkg, root_pkg, + target, cx)); + let rustc_cmd = mem::replace(work, proc() Ok(())); + let replacement = proc() { try!(rustc_cmd()); execute_cmd() }; + mem::replace(work, replacement); + } + } + + rustc }; let dst = match (target.is_lib(), @@ -207,7 +199,34 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package, dst.push((job(dirty, fresh, desc), freshness)); } } - jobs.enqueue(pkg, jq::StageCustomBuild, builds); + + if builds.len() >= 1 { + // New custom build system + jobs.enqueue(pkg, jq::StageCustomBuild, builds); + + } else { + // Old custom build system + // TODO: deprecated, remove + let mut build_cmds = Vec::new(); + for (i, build_cmd) in pkg.get_manifest().get_build().iter().enumerate() { + let work = try!(compile_custom_old(pkg, build_cmd.as_slice(), cx, i == 0)); + build_cmds.push(work); + } + let (freshness, dirty, fresh) = + try!(fingerprint::prepare_build_cmd(cx, pkg)); + let desc = match build_cmds.len() { + 0 => String::new(), + 1 => pkg.get_manifest().get_build()[0].to_string(), + _ => format!("custom build commands"), + }; + let dirty = proc() { + for cmd in build_cmds.into_iter() { try!(cmd()) } + dirty() + }; + jobs.enqueue(pkg, jq::StageCustomBuild, vec![(job(dirty, fresh, desc), + freshness)]); + } + jobs.enqueue(pkg, jq::StageLibraries, libs); jobs.enqueue(pkg, jq::StageBinaries, bins); jobs.enqueue(pkg, jq::StageTests, tests); @@ -288,6 +307,74 @@ fn compile_custom_old(pkg: &Package, cmd: &str, }) } +// Prepares a `Work` that executes the target as a custom build script. +// `pkg` is the package the build script belongs to, and `root_pkg` is the package +// Cargo is being run on. +fn prepare_execute_custom_build(pkg: &Package, root_pkg: &Package, target: &Target, + cx: &mut Context) -> CargoResult { + // TODO: this shouldn't explicitly pass `KindTarget` for dest/deps_dir, we + // may be building a C lib for a plugin + let layout = cx.layout(pkg, KindTarget); + let output = layout.native(pkg); + let old_output = layout.proxy().old_native(pkg); + + // Building the command to execute + let to_exec = try!(cx.target_filenames(target)); + if to_exec.len() >= 2 { + return Err(human(format!("custom build script shouldn't have multiple outputs"))); + } + let to_exec = to_exec.into_iter().next(); + let to_exec = match to_exec { + Some(cmd) => cmd, + None => return Err(human(format!("failed to determine output of custom build script"))), + }; + let to_exec = layout.root().join(to_exec); + + let profile = target.get_profile(); + let mut p = process(to_exec, pkg, cx) + .env("OUT_DIR", Some(&output)) + .env("CARGO_MANIFEST_DIR", Some(root_pkg.get_manifest_path() + .display().to_string())) + .env("NUM_JOBS", profile.get_codegen_units().map(|n| n.to_string())) + .env("TARGET", Some(cx.target_triple())) + .env("DEBUG", Some(profile.get_debug().to_string())) + .env("OPT_LEVEL", Some(profile.get_opt_level().to_string())) + .env("PROFILE", Some(profile.get_env())); + + match cx.resolve.features(pkg.get_package_id()) { + Some(features) => { + for feat in features.iter() { + let feat = feat.as_slice().chars() + .map(|c| c.to_uppercase()) + .map(|c| if c == '-' {'_'} else {c}) + .collect::(); + p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1")); + } + } + None => {} + } + + let pkg = pkg.to_string(); + + Ok(proc() { + // TODO: is this necessary? it's already part of layout::prepare + try!(if old_output.exists() { + fs::rename(&old_output, &output) + } else { + fs::mkdir(&output, USER_RWX) + }.chain_error(|| { + internal("failed to create output directory for build command") + })); + + try!(p.exec_with_output().map(|_| ()).map_err(|mut e| { + e.msg = format!("Failed to run custom build command for `{}`\n{}", + pkg, e.msg); + e.mark_human() + })); + Ok(()) + }) +} + fn rustc(package: &Package, target: &Target, cx: &mut Context, req: PlatformRequirement) -> CargoResult >{ diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index 01d3beb5d..82435718b 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -766,8 +766,10 @@ fn normalize(libs: &[TomlLibTarget], fn custom_build_target(dst: &mut Vec, cmd: &Path, profiles: &TomlProfiles) { let profiles = [ - merge(Profile::default_dev().for_host(true), &profiles.dev), - merge(Profile::default_release().for_host(true), &profiles.release), + merge(Profile::default_dev().for_host(true).custom_build(true), + &profiles.dev), + merge(Profile::default_release().for_host(true).custom_build(true), + &profiles.release), ]; let name = format!("build-script-{}", cmd.filestem_str().unwrap_or("")); diff --git a/tests/test_cargo_compile_custom_build.rs b/tests/test_cargo_compile_custom_build.rs index 0853d9dc1..aab7e6556 100644 --- a/tests/test_cargo_compile_custom_build.rs +++ b/tests/test_cargo_compile_custom_build.rs @@ -12,7 +12,7 @@ test!(custom_build_compiled { name = "foo" version = "0.5.0" authors = ["wycats@example.com"] - build = 'build.rs' + build = "build.rs" "#) .file("src/main.rs", r#" fn main() {} @@ -23,3 +23,100 @@ test!(custom_build_compiled { assert_that(p.cargo_process("build"), execs().with_status(101)); }) + +test!(custom_build_script_failed { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + "#) + .file("src/main.rs", r#" + fn main() {} + "#) + .file("build.rs", r#" + fn main() { + std::os::set_exit_status(101); + } + "#); + assert_that(p.cargo_process("build"), + execs().with_status(101) + .with_stderr(format!("\ +Failed to run custom build command for `foo v0.5.0 (file://{})` +Process didn't exit successfully: `{}` (status=101)", +p.root().display(), p.bin("build-script-build").display()))); +}) + +test!(custom_build_env_vars { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [features] + bar_feat = ["bar/foo"] + + [dependencies.bar] + path = "bar" + "#) + .file("src/main.rs", r#" + fn main() {} + "#) + .file("bar/Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + + [features] + foo = [] + "#) + .file("bar/src/lib.rs", r#" + pub fn hello() {} + "#); + + let file_content = format!(r#" + use std::os; + use std::io::fs::PathExtensions; + fn main() {{ + let _target = os::getenv("TARGET").unwrap(); + + let _ncpus = os::getenv("NUM_JOBS").unwrap(); + + let out = os::getenv("CARGO_MANIFEST_DIR").unwrap(); + let p1 = Path::new(out); + let p2 = os::make_absolute(&Path::new(file!()).dir_path().dir_path()); + assert!(p1 == p2, "{{}} != {{}}", p1.display(), p2.display()); + + let opt = os::getenv("OPT_LEVEL").unwrap(); + assert_eq!(opt.as_slice(), "0"); + + let opt = os::getenv("PROFILE").unwrap(); + assert_eq!(opt.as_slice(), "compile"); + + let debug = os::getenv("DEBUG").unwrap(); + assert_eq!(debug.as_slice(), "true"); + + let out = os::getenv("OUT_DIR").unwrap(); + assert!(out.as_slice().starts_with(r"{0}")); + assert!(Path::new(out).is_dir()); + + let _feat = os::getenv("CARGO_FEATURE_FOO").unwrap(); + }} + "#, + p.root().join("target").join("native").display()); + + let p = p.file("bar/build.rs", file_content); + + + assert_that(p.cargo_process("build").arg("--features").arg("bar_feat"), + execs().with_status(0)); +}) -- 2.30.2